package com.qudian.pay.redis.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import com.qudian.pay.redis.exception.LockException;
import com.qudian.pay.redis.support.ICache;

/**
 * @Description: 基于redis的分布式锁实现
 * @ClassName: DistributedLock
*
 * @date 2016年6月8日 下午3:08:26
 */
public final class DistributedLock implements Lock {
	
	private ICache<String> cache;
	
	/**
	 * @Fields lockKey : 锁的键值
	 */
	private String lockKey;
	
	/**
	 * @Fields expireMsecs : 锁生存时间<单位:毫秒>
	 */
	private long expireMsecs = 10 * 60 * 1000;
	
	/**
	 * @Fields timeoutMsecs : 超时时间<单位:毫秒>
	 */
	private long timeoutMsecs = 60 * 1000;
	
	/**
	 * @Fields locked : 是否锁着
	 */
	private boolean locked = false;
	
	public DistributedLock(ICache<String> cache, String lockKey) {
		this.cache = cache;
		this.lockKey = lockKey;
	}
	
	public DistributedLock(ICache<String> cache, String lockKey, long timeoutMsecs) {
		this(cache, lockKey);
		this.timeoutMsecs = timeoutMsecs;
	}
	
	public DistributedLock(ICache<String> cache, String lockKey, long timeoutMsecs, long expireMsecs) {
		this(cache, lockKey, timeoutMsecs);
		this.expireMsecs = expireMsecs;
	}
	
	/**
	 * 此方法为在超时范围内阻塞式获取锁，如果超时仍然没有获得锁，将不中断，继续进行后续操作
	 */
	@Override
	public synchronized void lock() {
		long timeout = timeoutMsecs;
		while (timeout >= 0) {
			if(tryLock()) return;
			timeout -= 100;
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				throw new LockException(-1, e);
			}
		}
	}
	
	/**
	 * 此方法为在超时范围内阻塞式获取锁，如果超时仍然没有获得锁，将抛出异常不执行后续操作
	 */
	@Override
	public synchronized void lockInterruptibly() throws InterruptedException {
		long timeout = timeoutMsecs;
		while (timeout >= 0) {
			if(tryLock()) return;
			timeout -= 100;
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				throw new LockException(-1, e);
			}
		}
		throw new LockException(-1, "获取分布式锁超时");
	}
	
	/**
	 * 此方法为非阻塞式获取锁
	 */
	@Override
	public synchronized boolean tryLock() {
		String expiresStr = String.valueOf(System.currentTimeMillis() + expireMsecs + 1);
		if (cache.setNx(lockKey, expiresStr)) {
			cache.expire(lockKey, (int) TimeUnit.MILLISECONDS.toSeconds(expireMsecs) + 30);
			locked = true;
			return true;
		}
		String currentValueStr = cache.get(lockKey,String.class);
		if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
			String oldValueStr = cache.getAndSet(lockKey, expiresStr,String.class);
			cache.expire(lockKey, (int) TimeUnit.MILLISECONDS.toSeconds(expireMsecs) + 30);
			if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
				locked = true;
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 此方法为在给定时间范围内的非阻塞式获取锁(给定时间为阻塞时间,每20ms获取一次)
	 */
	@Override
	public synchronized boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		long timeout = unit.toMillis(time);
		while (timeout >= 0) {
			if(tryLock()) return true;
			timeout -= 20;
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
				throw new LockException(-1, e);
			}
		}
		return false;
	}
	
	@Override
	public synchronized void unlock() {
		if (locked) {
			cache.delete(lockKey);
			locked = false;
		}
	}
	
	/**
	 * 暂不支持
	 */
	@Override
	public Condition newCondition() {
		return null;
	}
}
